package com.nulldev.util.web.nTube.pages;

import static com.nulldev.util.web.nTube.pages.Urlizer.urlizer;

import java.io.IOException;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import com.nulldev.util.JVM.Arguments;
import com.nulldev.util.VariableAPI.ArrayUtils;
import com.nulldev.util.VariableAPI.MathUtil;
import com.nulldev.util.VariableAPI.StringsUtil;
import com.nulldev.util.VariableAPI.util.strings.splitters.Splitters;
import com.nulldev.util.codecs.uniapi.UniBase64;
import com.nulldev.util.concurrency.threadIt.v4.asyncTasks.AsyncTaskCallback;
import com.nulldev.util.concurrency.threadIt.v4.emapi.ExecutorManager;
import com.nulldev.util.concurrency.threadIt.v4.emapi.IExecutorManager;
import com.nulldev.util.concurrency.threadIt.v4.emapi.cntft.ControllableFutureTask;
import com.nulldev.util.concurrency.threadIt.v4.emapi.uft.Executable;
import com.nulldev.util.concurrency.threadIt.v4.utils.impls.TaskUtils;
import com.nulldev.util.data.Variables;
import com.nulldev.util.data.variable;
import com.nulldev.util.data.Arrays.arrays.FastLists;
import com.nulldev.util.data.Arrays.arrays.MultiComparator;
import com.nulldev.util.data.Arrays.maps.FastMaps;
import com.nulldev.util.internal.backport.arrays.BackportList;
import com.nulldev.util.io.IOUtils;
import com.nulldev.util.languages.json.uniapi.UniJsonValue;
import com.nulldev.util.logging.Logger;
import com.nulldev.util.logging.LoggerFactory;
import com.nulldev.util.manage.Version;
import com.nulldev.util.manage.experiments.Experiments;
import com.nulldev.util.web.HttpServer3.Response;
import com.nulldev.util.web.HttpServer3.legacyPages.v1.RequestType;
import com.nulldev.util.web.HttpServer3.pages.v3.ASyncHttpPage3;
import com.nulldev.util.web.URLUtils.URLQueryParser;
import com.nulldev.util.web.URLUtils.URLQueryParser.URLQueryParserOptions;
import com.nulldev.util.web.YouTubeExtractor.YouTubeExtractor;
import com.nulldev.util.web.YouTubeExtractor.YouTubeExtractor.ExtractorType;
import com.nulldev.util.web.YouTubeExtractor.YouTubeExtractorConstants;
import com.nulldev.util.web.YouTubeExtractor.clarifications.Clarification;
import com.nulldev.util.web.YouTubeExtractor.comments.YTComments.YTCommentInitData;
import com.nulldev.util.web.YouTubeExtractor.endscreen.EndscreenEntry;
import com.nulldev.util.web.YouTubeExtractor.extractors.IYouTubeExtractor;
import com.nulldev.util.web.YouTubeExtractor.extractors.IYouTubeExtractorConstants;
import com.nulldev.util.web.YouTubeExtractor.extractors.util.YTIAPIClientType;
import com.nulldev.util.web.YouTubeExtractor.formats.AudioCodec;
import com.nulldev.util.web.YouTubeExtractor.formats.Format;
import com.nulldev.util.web.YouTubeExtractor.formats.VideoCodec;
import com.nulldev.util.web.YouTubeExtractor.items.YouTubeChapterEntry;
import com.nulldev.util.web.YouTubeExtractor.items.YouTubeStoryboardParser;
import com.nulldev.util.web.YouTubeExtractor.items.YouTubeStoryboardParser.StoryboardSection;
import com.nulldev.util.web.YouTubeExtractor.items.YouTubeStoryboardParser.YouTubeStoryboard;
import com.nulldev.util.web.YouTubeExtractor.items.YouTubeVideoHeatmap;
import com.nulldev.util.web.YouTubeExtractor.metadata.YouTubeMetadata;
import com.nulldev.util.web.YouTubeExtractor.metadata.YouTubeMetadata.DRMMetadata;
import com.nulldev.util.web.YouTubeExtractor.metadata.YouTubeMetadata.DRMMetadata.DRMFamily;
import com.nulldev.util.web.YouTubeExtractor.playlist.Playlist;
import com.nulldev.util.web.YouTubeExtractor.playlist.Playlist.PlaylistEntry;
import com.nulldev.util.web.YouTubeExtractor.related.RelatedParser;
import com.nulldev.util.web.YouTubeExtractor.related.RelatedParser.RelatedInfo;
import com.nulldev.util.web.YouTubeExtractor.related.VideoInfo;
import com.nulldev.util.web.YouTubeExtractor.subtitles.YouTubeCaptions;
import com.nulldev.util.web.YouTubeExtractor.video.ExtractedVideo;
import com.nulldev.util.web.YouTubeExtractor.video.YouTubeVideo;
import com.nulldev.util.web.YouTubeExtractor.video.YouTubeVideo.FormatType;
import com.nulldev.util.web.nTube.NTubeLaunchArguments.nTubeArguments;
import com.nulldev.util.web.nTube.NTubeServer;
import com.nulldev.util.web.nTube.components.video_queue.VideoQueueThread;
import com.nulldev.util.web.nTube.pages.misc.DJSCache;
import com.nulldev.util.web.nTube.util.APILevels;
import com.nulldev.util.web.nTube.util.APIWarnings;
import com.nulldev.util.web.nTube.util.DashMPDGenerator2;
import com.nulldev.util.web.nTube.util.VideoPageUtil;
import com.nulldev.util.web.nTubeExtensions.yte_for_archives.YTEArchiveExtractor;

public class GetVideoAsyncPage extends ASyncHttpPage3 {

	private final VideoQueueThread VIDEO_QUEUE;

	public GetVideoAsyncPage(final VideoQueueThread VIDEO_QUEUE) {
		super("/api/get_video");
		setCachingMethod(CachingMethod.NO_CACHE);
		setUseJSONCaptchaPage(true);
		this.VIDEO_QUEUE = Variables.requireNonNullObject(VIDEO_QUEUE, "VIDEO_QUEUE");
	}

	/**
	 * Progress tracker:
	 * 
	 * Ongoing: <br>
	 * - Enable the new sorting algo. by default.
	 * 
	 * Planned: <br>
	 * - Add extractionType's <br>
	 * - Better missing audio handling (part 2) [ONGOING] <br>
	 * - API Level 1.69: Add a new versioning scheme that allows X.Y.ZZ [DONE] <br>
	 * The old versioning scheme will be frozen at 1.6.9 and then clients should
	 * refer to the new one. <br>
	 * - API Level 1.7 [ONGOING] <br>
	 * <br>
	 * Changes: <br>
	 * - Remove isVR180. [DONE] <br>
	 * - Add projectionMethod to videos. <br>
	 * - Move true_fps and fps to an object. (framerate) <br>
	 * - Descriptions are no longer in Base64. [DONE] <br>
	 * - Tags are now in a JSON array. [DONE] <br>
	 * - Upcoming content has it's own timestamp instead of message. (for
	 * multi-timezone support) [DONE] <br>
	 * - Remove storyboard and replace with storyboards array. (ABI 5005)
	 * 
	 * Completed: <br>
	 * - Better missing audio handling (part 1) <br>
	 * - VideoRequestProfile implementation. <br>
	 * - Moving MPEG-DASH MPD generators away from here. <br>
	 * - Refresh the pref. video algo. <br>
	 * - Use ExecutorManager::submit(Returnable<T>) instead of FutureTask<T>'s <br>
	 * - Prepare for ThreadDispatcher->ExecutorManager switch. <br>
	 * - Switch from Map<String, List<String>> to Map<String, String> for requests
	 */

	private final static IExecutorManager td = ExecutorManager.get();

	public final static Version VIDEO_API_STABLE_VER_NS = new Version(1, 6, 9);
	public final static Version VIDEO_API_UPCOMING_VER_NS = new Version(1, 7, 0);

	/* Frozen, use VIDEO_API_STABLE_VER_NS instead. */
	public final static double VIDEO_API_STABLE_VER = 1.69;
	/* Frozen, use VIDEO_API_UPCOMING_VER_NS instead. */
	public final static double VIDEO_API_UPCOMING_VER = 1.7;

	private final static Logger log = LoggerFactory.getLoggerDT(Arguments.hasArgument("--debug", "--trace", "--GetVideoPage.debug", "--GetVideoPage.timing"));
	private final static boolean TIMING_LOG = Arguments.hasArgument("--GetVideoPage.timingLog", "--GetVideoPage.timingDebug");
	private final static boolean ENABLE_NEW_SORTER = Arguments.hasArgument("--GetVideoPage.useNewSorter");
	public final static boolean ENABLE_LATE_EXTRACTIONS = Experiments.get("ntube://enable-late-extractions/").isActive();
	private final static boolean USE_PARALLEL_STREAM_GEN = Experiments.get("ntube://parallel-stream-gen/").isActive();

	private final static boolean EXPOSE_IPs = Arguments.hasArgument("--NTubeServer.exposeIPs");

	private final static String RELOAD_NOW_JSON = "{\"reload\":\"now\",\"error_id\":\"ERROR_INTERNAL_EXCEPTION\"}";
	public final static String BAD_REQUEST_JSON = "{\"error_id\":\"ERROR_INTERNAL_EXCEPTION\",\"error_message\":\"Invalid request.\"}";

	public final static Comparator<YouTubeVideo> AS_BITRATE_COMP = new Comparator<YouTubeVideo>() {

		@Override
		public int compare(final YouTubeVideo o1, final YouTubeVideo o2) {
			if (o1 == null || o2 == null)
				return 0;
			return o1.getFormat().getBitrate() - o2.getFormat().getBitrate();
		}
	};
	public static final Comparator<YouTubeVideo> CS_HEIGHT_COMP = new Comparator<YouTubeVideo>() {

		@Override
		public int compare(final YouTubeVideo o1, final YouTubeVideo o2) {
			if (o1 == null || o2 == null)
				return 0;
			return o1.getFormat().getHeight() - o2.getFormat().getHeight();
		}
	};
	public static final Comparator<YouTubeVideo> VS_HEIGHT_COMP = new Comparator<YouTubeVideo>() {

		@Override
		public int compare(final YouTubeVideo o1, final YouTubeVideo o2) {
			if (o1 == null || o2 == null)
				return 0;
			return o1.getFormat().getHeight() - o2.getFormat().getHeight();
		}
	};
	public static final Comparator<YouTubeVideo> VS_FPS_COMP = new Comparator<YouTubeVideo>() {

		@Override
		public int compare(final YouTubeVideo o1, final YouTubeVideo o2) {
			if (o1 == null || o2 == null)
				return 0;
			return o1.getFormat().getFPS() - o2.getFormat().getFPS();
		}
	};

	private static final String JSON_MIME = "application/json; charset=utf-8";

	@SuppressWarnings("resource")
	@Override
	public void onFutureRequest(final Request _request, final ControllableFutureTask<Response> r) {
		final String args = _request.url();
		String body = null;
		if (_request.isType(RequestType.POST) && !_request.hasData()) {
			log.warn("Request didn't contain required data.");
			r.complete(new Response(400));
			return;
		}
		if (!_request.hasData() && log.isDebugEnabled()) {
			log.info("RequestType: " + _request.getType());
			log.info("hasData: false");
			log.info("data_stream: " + _request.getInputStream());
		}
		if (_request.hasData()) {
			try {
				body = new String(IOUtils.toByteArray(_request.getInputStream()));
			} catch (IOException e) {
				log.error("Failed to read body!", e);
			}
			if (!body.startsWith("{") && !body.endsWith("}")) {
				r.complete(new Response(400));
				return;
			}
		}
		UniJsonValue nextAPIBody = null;
		if (body != null && !body.isEmpty()) {
			nextAPIBody = UniJsonValue.parse(body);
		} else if (body != null && body.isEmpty()) {
			log.warn("Read body but it was empty...");
		} else if (body == null && _request.hasData()) {
			log.warn("Request had data and we didn't read it...");
		} else {
			nextAPIBody = UniJsonValue.emptyObject();
		}
		log.trace("Starting get_vid command.");
		final Map<String, String> request = URLQueryParser.extract(args, URLQueryParserOptions.PREFER_EXPERIMENTAL, URLQueryParserOptions.TREAT_URL_AS_QUERY);
		final VideoRequestProfile vrp = new VideoRequestProfile();
		final int nextAPILevel = nextAPIBody != null && !nextAPIBody.isNull() ? nextAPIBody.getInt("next_api_version", 1) : 0;
		if (nextAPIBody != null && !nextAPIBody.isNull() && nextAPILevel >= 2) {
			vrp.apply(nextAPIBody);
		} else {
			if (request != null && !request.isEmpty()) {
				vrp.apply(request);
			}
			if (nextAPIBody != null && !nextAPIBody.isNull()) {
				vrp.apply(nextAPIBody);
			}
		}
		if (!vrp.isValidRequest()) {
			r.complete(new Response(200, createStream(BAD_REQUEST_JSON)).setCustomMime(JSON_MIME));
			return;
		}
		final boolean useBitChute = request.containsKey("bc") || nextAPIBody.bool("isBitChute", false);
		if (useBitChute || vrp.videoID.length() >= 12) {
			__bitchute(vrp, r);
			return;
		} else {
			__yt(_request, request, vrp, nextAPIBody, nextAPILevel, r);
			return;
		}
	}

	public static class TelemetryData {
		public final boolean supportsH264;
		public final boolean supportsVP9;
		public final boolean supportsAV1;
		public final String client;

		public TelemetryData(final UniJsonValue telemetry) {
			this.client = telemetry.getString("client", "N/A");
			this.supportsH264 = telemetry.bool("h264", false);
			this.supportsVP9 = telemetry.bool("vp9", false);
			if (this.client.equalsIgnoreCase("Safari 10")) {
				log.warn("[TelemetryData] Applying workaround for browsers that incorrectly claim AV1 support.");
				this.supportsAV1 = false;
			} else {
				this.supportsAV1 = telemetry.bool("av1", false);
			}
		}
	}

	public static class VideoRequestProfile {
		/**
		 * 16k resolution, very unlikely that YouTube will be adopting this anytime
		 * soon.
		 */
		private static final int MAX_QUALITY = 8640;

		/* */

		public boolean dataSaving; /* was isDataSaver */
		public boolean openCodecsOnly; /* was _onlyWebM */
		public int preferredQuality = 1080; /* was preferredVideoQuality, previously 720p. */
		public String videoID;
		public boolean reduceLoad;
		public boolean av1_only_lowRes;
		public boolean enableAV1 = true; /* was disableAV1codecs, then disableAV1 */
		public boolean enableHDR; /* was useYUV420p10 */
		public boolean allowWebMs = true; /* allowWebM */
		public boolean enableDASH; /* was enableDJSMuxer */
		public boolean dashRemove30FPS; /* was djsRemove30FPSVideos */
		public boolean dashRemove60FPS; /*
										 * Conflicts with dashRemove30FPS, dashRemove30FPS takes higher priority in
										 * code.
										 */
		public boolean dash_use_server_caching; /* was dashJSAllowCaching */
		public boolean dash_use_compression; /* was dashJSUseCompression */
		public String extractionType = "page";
		public boolean audioOnly;
		public boolean allow_lazy_video_pref = false;
		public boolean include_fallback_urls = false;
		/* I'm talking about YOU Firefox. */
		public boolean disable_webvtt_styling = false;
		public boolean force_aac_nplayer = false;
		public boolean random_video_cdn = false;
		public boolean use_ios_hls = false;
		/* fuckAdvertising: Total Advertiser Death(TM) */
		public boolean enable_fad_tad = false;
		/* nTube Archive Viewer */
		public String av_date = null;

		/* systemInfo */
		/**
		 * The result from <code>Date.[prototype].getTimezoneOffset()</code> in minutes.
		 * <br>
		 * Use <code>-1</code> if you want to disable timezone adjusted dates and
		 * timestamps.
		 */
		public int systemInfo_UTC_TIMEZONE_OFFSET = -1;

		public boolean isValidRequest() {
			if (this.preferredQuality == -1)
				this.preferredQuality = MAX_QUALITY;
			return this.videoID != null && !this.videoID.isEmpty() && !this.videoID.equalsIgnoreCase("null");
		}

		private static String getValue(final String id, final String fallback, final Map<String, String> source) {
			if (id == null || id.isEmpty() || source == null || source.isEmpty())
				return fallback;
			return source.getOrDefault(id, fallback);
		}

		private static boolean isPresent(final String id, final boolean fallback, final Map<String, String> source) {
			if (id == null || id.isEmpty() || source == null || source.isEmpty())
				return fallback;
			if (!source.containsKey(id))
				return fallback;
			return true;
		}

		public void apply(final Map<String, String> request) {
			if (request == null || request.isEmpty())
				throw new NullPointerException("request is null or empty!");
			this.videoID = getValue("v", this.videoID, request);
			this.allowWebMs = isPresent("allowWebM", this.allowWebMs, request);
			this.enableDASH = isPresent("useDJSMuxingViaMPD", this.enableDASH, request);
			this.preferredQuality = MathUtil.safeParseInt(getValue("prefer", "1080", request), this.preferredQuality);
			this.dash_use_server_caching = isPresent("dashJSAllowCaching", this.dash_use_server_caching, request);
			this.dashRemove30FPS = isPresent("mpdForce60", this.dashRemove30FPS, request);
			this.dashRemove60FPS = isPresent("mpdForce30", this.dashRemove60FPS, request);
			this.enableAV1 = isPresent("av1", this.enableAV1, request);
			this.dash_use_compression = isPresent("dashJSUseCompression", this.dash_use_compression, request) && !this.dash_use_server_caching;
			this.dataSaving = isPresent("dataSaver", this.dataSaving, request);
			this.enableHDR = isPresent("enableHDR", this.enableHDR, request);
			this.audioOnly = isPresent("audio", this.audioOnly, request);
			this.allow_lazy_video_pref = isPresent("lazy_vp", this.allow_lazy_video_pref, request);
			this.include_fallback_urls = isPresent("include_fallback_urls", this.include_fallback_urls, request);
			this.disable_webvtt_styling = isPresent("disable_webvtt_styling", this.disable_webvtt_styling, request);
			this.av1_only_lowRes = isPresent("av1_only_lowRes", this.av1_only_lowRes, request);
			this.force_aac_nplayer = isPresent("nplayer_force_aac", this.force_aac_nplayer, request);
			this.random_video_cdn = isPresent("random_video_cdn", this.random_video_cdn, request);
			this.enable_fad_tad = isPresent("enable_fad_tad", this.enable_fad_tad, request);
			this.use_ios_hls = isPresent("use_ios_hls", this.use_ios_hls, request);
			this.av_date = getValue("av_date", this.av_date, request);
			if (this.preferredQuality < 144)
				this.preferredQuality = -1;
			if (this.dataSaving)
				this.preferredQuality = 360;
			if (this.openCodecsOnly)
				this.allowWebMs = true;
			if (this.dashRemove30FPS && this.dashRemove60FPS)
				this.dashRemove60FPS = false;
		}

		public void apply(final UniJsonValue object) {
			if (object == null || object.isNull())
				throw new NullPointerException("object is null!");
			this.videoID = object.getString("videoID", this.videoID);
			this.allowWebMs = object.bool("enableWebM", this.allowWebMs);
			this.enableDASH = object.bool("enableDashJS", this.enableDASH);
			this.preferredQuality = object.getInt("preferredQuality", this.preferredQuality);
			this.dash_use_server_caching = object.bool("dashJSAllowCaching", this.dash_use_server_caching);
			this.dash_use_compression = object.bool("dashJSUseCompression", this.dash_use_compression) && !this.dash_use_compression;
			this.dashRemove30FPS = object.bool("dashJS_force60FPS", this.dashRemove30FPS);
			this.dashRemove60FPS = object.bool("dashJS_force30FPS", this.dashRemove60FPS);
			this.enableAV1 = object.bool("enableAV1", this.enableAV1);
			this.dataSaving = object.bool("dataSaver", this.dataSaving);
			this.enableHDR = object.bool("enableHDR", this.enableHDR);
			this.reduceLoad = object.bool("reduceLoad", this.reduceLoad);
			this.audioOnly = object.bool("audioOnly", this.audioOnly);
			this.allow_lazy_video_pref = object.bool("lazy_vp", this.allow_lazy_video_pref);
			this.include_fallback_urls = object.bool("include_fallback_urls", this.include_fallback_urls);
			this.disable_webvtt_styling = object.bool("disable_webvtt_styling", this.disable_webvtt_styling);
			this.av1_only_lowRes = object.bool("av1_only_lowRes", this.av1_only_lowRes);
			this.force_aac_nplayer = object.bool("nplayer_force_aac", this.force_aac_nplayer);
			this.random_video_cdn = object.bool("random_video_cdn", this.random_video_cdn);
			this.enable_fad_tad = object.bool("enable_fad_tad", this.enable_fad_tad);
			this.use_ios_hls = object.bool("use_ios_hls", this.use_ios_hls);
			this.av_date = object.getString("av_date", this.av_date);
			if (object.contains("systemInfo")) {
				this.systemInfo_UTC_TIMEZONE_OFFSET = object.get("systemInfo").getInt("utcTimezoneOffset", -1);
			}
			if (this.preferredQuality < 144)
				this.preferredQuality = -1;
			if (this.dataSaving)
				this.preferredQuality = 360;
			if (this.openCodecsOnly)
				this.allowWebMs = true;
			if (this.dashRemove30FPS && this.dashRemove60FPS)
				this.dashRemove60FPS = false;
		}
	}

	private UniJsonValue __yt_process_metadata(final YouTubeMetadata metadata, final boolean isAPI17, final IYouTubeExtractor ext,
			final ExtractedVideo result) {
		final UniJsonValue meta = UniJsonValue.object(23);
		meta.add("title", metadata.getTitle());
		meta.add("duration", metadata.getDuration());
		meta.add("rating", metadata.getRating());
		try {
			meta.add("percentRating", (metadata.getRating() / 5 * 100));
		} catch (Exception e) {
			log.error("Failed to generate percentRating.", e);
		}
		if (isAPI17) {
			final UniJsonValue tags = UniJsonValue.array(metadata.getTags().length);
			for (final String tag : metadata.getTags()) {
				tags.add(tag);
			}
			meta.add("tags", tags);
		} else
			meta.add("tags", UniBase64.encodeFromString(metadata.getTagsFormatted()));
		meta.add("author", metadata.getAuthor());
		meta.add("authorID", metadata.getAuthorID());
		meta.add("views", metadata.getViewCount());
		if (isAPI17)
			meta.add("description", metadata.getEnhancedDescription(true));
		else
			meta.add("description", UniBase64.encodeFromString(metadata.getEnhancedDescription(true)));
		meta.add("limitedExtractionData", metadata.limitedExtraction());
		meta.add("isLiveStream", metadata.isLiveStream());
		meta.add("isDVR", metadata.isDVRStream());
		meta.add("isLowLatencyStream", metadata.isLowLatencyStream());
		if (isAPI17 && metadata.hasRawStoryboardURL()) {
			final UniJsonValue storyboard = UniJsonValue.object(4);
			storyboard.add("raw", metadata.getRawStoryboardURL());
			final YouTubeStoryboard sb = YouTubeStoryboardParser.parse(metadata.getRawStoryboardURL());
			storyboard.add("best", sb.getBestSectionSize());
			storyboard.add("sectionCount", sb.getSectionCount());
			final UniJsonValue sections = UniJsonValue.array(sb.getSectionCount());
			for (final StoryboardSection section : sb.getSections()) {
				final UniJsonValue sec = UniJsonValue.object(8);
				sec.add("columns", section.columns);
				sec.add("duration", section.duration);
				sec.add("height", section.height);
				sec.add("rows", section.rows);
				sec.add("width", section.width);
				sec.add("imageName", section.imageName);
				sec.add("quality", section.quality);
				sec.add("signatureHash", section.signatureHash);
				sections.add(sec);
			}
			storyboard.add("sections", sections);
			meta.add("storyboard", storyboard);
		} else {
			if (metadata.hasStoryboardURL()) {
				meta.add("storyboard", metadata.getStoryboardURL());
			}
			if (metadata.hasRawStoryboardURL()) {
				meta.add("raw_storyboard", metadata.getRawStoryboardURL());
			}
		}
		if (metadata.hasRawCaptionsURL()) {
			meta.add("raw_captions", metadata.getRawCaptionsURL());
		}
		if (metadata.hasThumbnailLevel()) {
			meta.add("thumbnailLevel", metadata.getThumbnailLevel());
		}
		if (metadata.hasContentStartTime()) {
			meta.add("contentStartTime", metadata.getContentStartTime());
		}
		if (metadata.is360()) {
			meta.add("is360", metadata.is360());
			if (!isAPI17)
				meta.add("isVR180", metadata.isVR180());
		}
		if (metadata.hasPlaybackMode()) {
			meta.add("playbackMode", metadata.getPlaybackMode().id);
		}
		if (metadata.hasLocalizedTitle()) {
			meta.add("localizedTitle", metadata.getLocalizedTitle());
		}
		if (result.hasCommentData()) {
			final UniJsonValue commentData = UniJsonValue.object(8);
			final YTCommentInitData ytc = result.getCommentData();
			commentData.add("clientName", ytc.getClientName());
			commentData.add("clientVersion", ytc.getClientVersion());
			commentData.add("token", ytc.getCommentToken());
			commentData.add("videoID", ytc.getVideoID());
			commentData.add("cookiePayload", ytc.getCookiePayload());
			commentData.add("xsrfToken", ytc.getXSRFToken());
			if (result.hasClientType())
				commentData.add("clientType", result.getClientType().getID());
			if (ext.isLegacyBackend())
				commentData.add("legacy", true);
			meta.add("comments", commentData);
		}
		if (result.hasChatPayload()) {
			final UniJsonValue chatData = UniJsonValue.object(2);
			chatData.add("cont", result.getChatPayload());
			chatData.add("url", "/proxy/live_chat?continuation=" + result.getChatPayload());
			meta.add("chat", chatData);
		}
		{
			final UniJsonValue ed = UniJsonValue.object(5);
			ed.add("familyFriendly", metadata.getExtendedMetadata().isFamilyFriendly);
			ed.add("publishDate", metadata.getExtendedMetadata().publishDate);
			ed.add("uploadDate", metadata.getExtendedMetadata().uploadDate);
			ed.add("rawLoudnessDb", metadata.getExtendedMetadata().loudnessDbRaw);
			ed.add("perceptualLoudnessDb", metadata.getExtendedMetadata().perceptualLoudnessDb);
			meta.add("extended", ed);
		}
		if (metadata.hasEndscreenEntries()) {
			final UniJsonValue endscreen = UniJsonValue.array(metadata.getEndscreenEntries().size());
			for (final EndscreenEntry e : metadata.getEndscreenEntries()) {
				final UniJsonValue o = UniJsonValue.object(6);
				o.add("type", e.getType().id);
				o.add("url", e.getURL());
				o.add("video_id", e.getVideoID());
				o.add("text", e.getText());
				o.add("subText", e.getSubText());
				o.add("img", e.getImageURL());
				endscreen.add(o);
			}
			meta.add("endscreen", endscreen);
		}
		if (metadata.hasClarifications()) {
			final UniJsonValue a = UniJsonValue.array(metadata.getClarifications().size());
			for (final Clarification e : metadata.getClarifications()) {
				final UniJsonValue o = UniJsonValue.object(4);
				o.add("title", e.getTitle());
				o.add("text", e.getText());
				o.add("subText", e.getSubText());
				o.add("url", e.getURL());
				a.add(o);
			}
			meta.add("clarifications", a);
		}
		if (metadata.hasSupertags()) {
			final UniJsonValue a = UniJsonValue.array(metadata.getSupertags().size());
			for (final String tag : metadata.getSupertags()) {
				a.add(tag);
			}
			meta.add("supertags", a);
		}
		if (metadata.hasLikeData()) {
			final UniJsonValue ld = UniJsonValue.object(2);
			ld.add("likes", metadata.getLikeCount());
			ld.add("dislikes", metadata.getDislikeCount());
			meta.add("likeData", ld);
		}
		if (metadata.hasHeatmap()) {
			final Executable<YouTubeVideoHeatmap> heat = metadata.getHeatmap();
			/*
			 * Don't wait for the heatmap, it should be ready by now, otherwise something
			 * went wrong.
			 */

			if (heat.completed()) {
				final YouTubeVideoHeatmap heatmap = heat.result();
				if (heatmap != null) {
					final UniJsonValue arr = UniJsonValue.array(heatmap.getSize());
					for (final Object[] data : heatmap.getHeatmap()) {
						if (data == null || data.length == 0)
							continue;
						final UniJsonValue entry = UniJsonValue.object(YouTubeVideoHeatmap.ENTRY_SET_SIZE);
						entry.add("heatmapPointDuration", MathUtil.autoConvertToLong(data[0]));
						entry.add("heatmapPointStartMillis", MathUtil.autoConvertToLong(data[1]));
						entry.add("heatmapIntensity", (float) data[2]);
						entry.add("isPriorityPoint", (boolean) data[3]);
						arr.add(entry);
					}
					meta.add("heatmap", arr);
				}
			} else {
				log.error("[__yt_process_metadata] Heatmap wasn't ready, disregarding in API call...");
				final String generated = TaskUtils.generate_trace(heat);
				log.error(generated);
			}
		}
		return meta;
	}

	@SuppressWarnings("resource")
	private void __yt(final Request rq, final Map<String, String> request, final VideoRequestProfile vrp, final UniJsonValue nextAPIBody,
			final int nextAPILevel, final ControllableFutureTask<Response> r) {
		log.trace("Using YouTube extractor...");
		final boolean isAPI17 = APILevels.API_LEVEL_1_7.isLevel(nextAPILevel);
		final long t = System.currentTimeMillis();
		final Executable<TelemetryData> telemetryData = td.asyncSubmit(() -> {
			if (nextAPIBody != null && !nextAPIBody.isNull() && nextAPIBody.contains("telemetry")) {
				final TelemetryData data = new TelemetryData(nextAPIBody.get("telemetry"));
				log.debug("telemetry: client=" + data.client + ",h264=" + data.supportsH264 + ",vp9=" + data.supportsVP9 + "[allowed=" + vrp.allowWebMs + "]"
						+ ",av1=" + data.supportsAV1 + "[allowed=" + vrp.enableAV1 + "],allowHDR=" + vrp.enableHDR);
				if (!data.supportsAV1 && !data.supportsH264 && data.supportsVP9) {
					vrp.openCodecsOnly = true;
					log.warn("telemetry: Data tells us that only VP9 is supported, forcing openCodecsOnly.");
				}
				return data;
			} else
				return null;
		});
		if ((rq.hasHeader("Save-Data") && rq.headerIs("Save-Data", "on")) || (rq.hasHeader("Sec-CH-Save-Data") && rq.headerIs("Sec-CH-Save-Data", "1"))) {
			vrp.dataSaving = true;
		}
		final boolean NTUBE_ARCHIVER_MODE = !StringsUtil.isZeroLengthOrNull(vrp.av_date);
		final IYouTubeExtractor ext;
		if (NTUBE_ARCHIVER_MODE) {
			ext = YTEArchiveExtractor.INSTANCE;
		} else if (vrp.use_ios_hls) {
			// XXX: Make an actual enum in YTE so you can pick a precise extractor in a
			// easier way.
			ext = YouTubeExtractor.get("ytiapi.native-rolling");
		} else {
			ext = YouTubeExtractor.get(ExtractorType.get(vrp.extractionType));
		}
		final Executable<ExtractedVideo> task;
		boolean successful_submission = false;
		if ((int) ext.getFlag(IYouTubeExtractorConstants.FLAG_EXTRACTOR_SUPPORTS_ABI_2_2) == IYouTubeExtractorConstants.FLAG_TRUE) {
			final Map<String, Object> map = FastMaps.uniMap(new Object[][]
				{
						{ IYouTubeExtractorConstants.MAP_TYPE_VIDEO_ID, vrp.videoID },
						{ IYouTubeExtractorConstants.MAP_TYPE_HEATMAP_DISABLE_PRIORITY_POINT, true } });
			if (NTUBE_ARCHIVER_MODE) {
				map.put(YTEArchiveExtractor.MAP_TYPE_NON_STANDARD_ARCHIVE_DATE, vrp.av_date);
				map.put(YTEArchiveExtractor.MAP_TYPE_NON_STANDARD_USE_NTUBE_ARCHIVE_PROXY, true);
			}
			if (request.containsKey("list"))
				map.put(IYouTubeExtractorConstants.MAP_TYPE_PLAYLIST_STR, request.get("list"));
			if (request.containsKey("index"))
				map.put(IYouTubeExtractorConstants.MAP_TYPE_PLAYLIST_INDEX, MathUtil.safeParseInt(request.get("index"), -1));
			if (request.containsKey("clar") || nextAPIBody.bool("clar", false))
				map.put(IYouTubeExtractorConstants.MAP_TYPE_ENABLE_CLARIFICATIONS, true);
			if (vrp.systemInfo_UTC_TIMEZONE_OFFSET != -1)
				map.put(IYouTubeExtractorConstants.MAP_TYPE_UTC_TIMEZONE_OFFSET, vrp.systemInfo_UTC_TIMEZONE_OFFSET);
			if (vrp.enable_fad_tad)
				map.put(IYouTubeExtractorConstants.MAP_TYPE_ENABLE_ADVERTISING_METADATA_EXTRACTION, true);
			if (vrp.use_ios_hls)
				map.put(IYouTubeExtractorConstants.MAP_TYPE_USE_CLIENT_TYPE, YTIAPIClientType.iOS);
			if (ENABLE_LATE_EXTRACTIONS)
				map.put(IYouTubeExtractorConstants.MAP_TYPE_ENABLE_LATE_EXTRACTIONS, true);
			final Executable<ExtractedVideo> _task = ext.generateTask(map);
			try {
				successful_submission = this.VIDEO_QUEUE.submit(_task);
				task = _task;
			} catch (InterruptedException e) {
				throw new RuntimeException(e);
			}
			log.debug("[GetVideoPage-async] task->dispatch (new)");
		} else {
			final UniJsonValue out = UniJsonValue.object(2);
			out.add("error_id", "ERROR_INCOMPATIBLE_EXTRACTOR");
			out.add("error_message", "This instance's extractor is using the pre-2.2 ABI, this is no longer supported.");
			r.complete(new Response(200, out, out).setCustomMime(JSON_MIME));
			return;
		}
		if (!successful_submission) {
			final UniJsonValue out = UniJsonValue.object(2);
			out.add("error_id", "ERROR_VIDEO_QUEUE_FULL");
			out.add("error_message", "This instance is currently very busy! Please try again later.");
			r.complete(new Response(200, out, out).setCustomMime(JSON_MIME));
			return;
		} else {
			final boolean task_accepted = task.addCallbackTask(new AsyncTaskCallback<ExtractedVideo>() {

				@Override
				public void onError(final Throwable e) {
					log.error("[GetVideoPage-async] onError::call", e);
					final UniJsonValue out = UniJsonValue.object(2);
					out.add("error_id", "ERROR_INTERNAL_EXCEPTION");
					out.add("error_message", "Hit '" + e.getClass().getSimpleName() + "' while fetching video.");
					r.complete(new Response(200, out, out).setCustomMime(JSON_MIME));
					return;
				}

				@Override
				public void onCompletion(final ExtractedVideo result) {
					log.debug("[GetVideoPage-async] onCompletion::call");
					final UniJsonValue out = UniJsonValue.object();
					if (TIMING_LOG) {
						log.debug("getVideoPage::YTE_API_RESULT -> " + (System.currentTimeMillis() - t) + " ms.");
					}
					final UniJsonValue warns = UniJsonValue.array();
					if (request.containsKey("extractor"))
						warns.add(APIWarnings.get(14));
					if (request.containsKey("audio"))
						warns.add(APIWarnings.get(15));
					if (isAPI17)
						warns.add(APIWarnings.get(29));
					else if (nextAPILevel > 3)
						warns.add(APIWarnings.get(30));
					if (VIDEO_API_STABLE_VER_NS.newerThan(1, 6, 10))
						warns.add(APIWarnings.get(32));
					out.add("warnings", warns);
					if (result == null) {
						out.add("error_id", "ERROR_RESULT_NULL");
					} else if (result.hasIDInfo() || !result.getResult().successful()) {
						// failed
						out.add("error_id", result.getIDInfo());
						if (result.hasYTInfo()) {
							out.add("error_message", result.getYTInfo());
						}
						if (result.getMetadata() != null) {
							final YouTubeMetadata metadata = result.getMetadata();
							out.add("metadata", GetVideoAsyncPage.this.__yt_process_metadata(metadata, isAPI17, ext, result));
						}
						final UniJsonValue env = UniJsonValue.object(5);
						{
							env.add("clientVersion", YouTubeExtractorConstants.CLIENT_VERSION);
							env.add("videoAPIVersion", isAPI17 ? 1.7 : VIDEO_API_STABLE_VER);
							env.add("videoAPIVersionNS", isAPI17 ? VIDEO_API_UPCOMING_VER_NS.asVersionString() : VIDEO_API_STABLE_VER_NS.asVersionString());
							env.add("serverVersion", NTubeServer.nTube_RELEASE);
							env.add("serverVersionNS", NTubeServer.nTube_VERSION.asVersionString());
						}
						out.add("environment", env);
						if (Arguments.notAnArgument(nTubeArguments._disableGC) && result != null) {
							result.clear();
							log.trace("Ran GC layer.");
						}
						r.complete(new Response(200, out, out).setCustomMime(JSON_MIME));
						return;
					} else {
						final YouTubeMetadata metadata = result.getMetadata();
						if (TIMING_LOG) {
							log.debug("getVideoPage::READ_METADATA -> " + (System.currentTimeMillis() - t) + " ms.");
						}
						final Executable<UniJsonValue> env_gen = td.asyncSubmit(() -> {
							final UniJsonValue env = UniJsonValue.object(5);
							env.add("clientVersion", YouTubeExtractorConstants.CLIENT_VERSION);
							env.add("videoAPIVersion", isAPI17 ? VIDEO_API_UPCOMING_VER : VIDEO_API_STABLE_VER);
							env.add("videoAPIVersionNS", isAPI17 ? VIDEO_API_UPCOMING_VER_NS.asVersionString() : VIDEO_API_STABLE_VER_NS.asVersionString());
							env.add("serverVersion", NTubeServer.nTube_RELEASE);
							env.add("serverVersionNS", NTubeServer.nTube_VERSION.asVersionString());
							return env;
						});
						final Executable<UniJsonValue> metadata_gen = td.asyncSubmit(() -> {
							return GetVideoAsyncPage.this.__yt_process_metadata(metadata, isAPI17, ext, result);
						});
						final Executable<UniJsonValue> related_gen = td.asyncSubmit(() -> {
							final UniJsonValue vdata = UniJsonValue.array(20);
							if (metadata.hasPageData() && !metadata.hasRelatedVideoOverride()) {
								final RelatedInfo vinfo = RelatedParser.parse(metadata.getPageData(), vrp.videoID);
								if (vinfo.size() == 0 && vinfo.hasContinuation()) {
									log.dwarn("Applying workaround for an empty related video array...");
									warns.add(APIWarnings.get(33));
									{
										final UniJsonValue contObj = UniJsonValue.object(3);
										contObj.add("workaround", true);
										contObj.add("clientType", result.getClientType().id);
										contObj.add("continuation", vinfo.getContinuation());
										vdata.add(contObj);
									}
								} else {
									for (final VideoInfo video : vinfo.related()) {
										final UniJsonValue v = UniJsonValue.object(10);
										v.add("title", video.getName());
										if (vrp.audioOnly) {
											v.add("link", "/watch?v=" + video.getID() + "&audio");
										} else {
											v.add("link", "/watch?v=" + video.getID());
										}
										v.add("imgURL",
												"https://i.ytimg.com/vi/" + video.getThumbnailID() + (vrp.dataSaving ? "/default.jpg" : "/mqdefault.jpg"));
										v.add("channel", video.getUser());
										v.add("id", video.getID());
										v.add("duration", video.getDuration());
										v.add("views", video.getViewCount());
										v.add("isPlaylist", video.isPlaylist());
										v.add("isUpNext", video.isUpnext());
										v.add("type", video.getType().getType());
										if (video.hasAnimatedThumbnail()) {
											v.add("ani_thumb", video.getAnimatedThumbnail());
										}
										vdata.add(v);
									}
								}
								log.debug("Registered " + vdata.length() + " videos. [m=parsed]");
							} else if (metadata.hasRelatedVideoOverride()) {
								final List<VideoInfo> vinfo = metadata.getRelatedVideoOverrides();
								for (final VideoInfo video : vinfo) {
									final UniJsonValue v = UniJsonValue.object(10);
									v.add("title", video.getName());
									if (vrp.audioOnly) {
										v.add("link", "/watch?v=" + video.getID() + "&audio");
									} else {
										v.add("link", "/watch?v=" + video.getID());
									}
									v.add("imgURL", "https://i.ytimg.com/vi/" + video.getThumbnailID() + (vrp.dataSaving ? "/default.jpg" : "/mqdefault.jpg"));
									v.add("channel", video.getUser());
									v.add("id", video.getID());
									v.add("duration", video.getDuration());
									v.add("views", video.getViewCount());
									v.add("isPlaylist", video.isPlaylist());
									v.add("isUpNext", video.isUpnext());
									v.add("type", video.getType().getType());
									if (video.hasAnimatedThumbnail()) {
										v.add("ani_thumb", video.getAnimatedThumbnail());
									}
									vdata.add(v);
								}
								log.debug("Registered " + vdata.length() + " videos. [m=override]");
							} else {
								log.debug("No related videos found.");
							}
							return vdata;
						});
						final Executable<UniJsonValue> captions_gen = td.asyncSubmit(() -> { // OLD-NOTE: submit() causes deadlocks, _NoAccel fixes that
							if (metadata.hasCaptions()) {
								final UniJsonValue captions = UniJsonValue.array(metadata.getCaptions().size());
								for (final YouTubeCaptions caption : metadata.getCaptions()) {
									final UniJsonValue v = UniJsonValue.object(5);
									v.add("text", caption.getText());
									v.add("code", caption.getLanguageCode());
									v.add("rawURL", caption.getURL());
									if (vrp.disable_webvtt_styling) {
										v.add("url", "/webvtt_gen?url=" + urlizer(caption.getURL()) + "&disable_styling=true");
									} else {
										v.add("url", "/webvtt_gen?url=" + urlizer(caption.getURL()));
									}
									v.add("proxied_url", urlizer(caption.getURL()));
									captions.add(v);
								}
								return captions;
							}
							return null;
						});
						final Executable<UniJsonValue> chapters_gen = td.asyncSubmit(() -> {
							if (metadata.hasChapters()) {
								final UniJsonValue vdata = UniJsonValue.array(metadata.getChapters().size());
								for (final YouTubeChapterEntry chapter : metadata.getChapters()) {
									final UniJsonValue obj = UniJsonValue.object(2);
									obj.add("title", chapter.text);
									obj.add("time", chapter.timeStart);
									vdata.add(obj);
								}
								return vdata;
							} else
								return null;
						});
						final Executable<UniJsonValue> drm_gen = td.asyncSubmit(() -> {
							if (metadata.hasDRMMetadata()) {
								warns.add(APIWarnings.get(24));
								final UniJsonValue vdata = UniJsonValue.object(4);
								final DRMMetadata drm = metadata.getDRMMetadata();
								vdata.add("drmParams", drm.getDRMParams());
								vdata.add("dashManifestUrl", drm.getDASHManifest());
								// TODO: Proxy-ify the DASH manifest.
								final UniJsonValue drmF = UniJsonValue.array(drm.getLicenseInfos().size());
								for (final DRMMetadata.LicenseInfo li : drm.getLicenseInfos()) {
									final UniJsonValue l = UniJsonValue.object(2);
									l.add("url", urlizer(li.getURL()));
									l.add("family", li.getFamily().name());
									drmF.add(l);
								}
								vdata.add("drmTypes", drmF);
								if (drm.hasWidevineCertificate()) {
									final UniJsonValue drmC = UniJsonValue.object(2);
									drmC.add("widevineServiceCert", drm.getWidevineCertificate());
									drmC.add("useInnertubeDrmService", drm.useInnertubeDrmService());
									vdata.add("drmConfig", drmC);
								}
								return vdata;
							} else {
								return null;
							}
						});
						final Executable<UniJsonValue> playlist_gen = td.asyncSubmit(() -> {
							if (result.hasPlaylist()) {
								boolean blockAdd = false;
								final UniJsonValue playlist = UniJsonValue.object(3);
								final Playlist pl = result.getPlaylist();
								if (pl.hasIndex())
									playlist.add("index", pl.getIndex());
								playlist.add("name", pl.getName());
								final UniJsonValue next = UniJsonValue.object(4);
								{
									final PlaylistEntry n = pl.next();
									if (n == null || n.getTitle() == null) {
										blockAdd = true;
									}
									if (!blockAdd) {
										try {
											next.add("title", n.getTitle());
											next.add("author", n.getUsername());
											next.add("thumbnail", n.getThumbnail());
											next.add("link", n.getLink());
										} catch (Exception e) {
											log.error(e);
											blockAdd = true;
										}
									}
								}
								if (!blockAdd) {
									playlist.add("next", next);
								} else if (playlist.isEmpty()) {
									return null;
								} else
									return null;
								return playlist;
							}
							return null;
						});
						if (TIMING_LOG) {
							log.debug("getVideoPage::SUBMIT_MAIN_TASKS -> " + (System.currentTimeMillis() - t) + " ms.");
						}
						log.debug("query::videos");
						if (result.supportsLateExtraction()) {
							log.debug("wait::late[" + result.getLateHold() + "]");
							if (TIMING_LOG) {
								log.debug("getVideoPage::KNOWN_LATE_EXTRACTION -> " + (System.currentTimeMillis() - t) + " ms.");
							}
							try {
								result.awaitCompletion();
							} catch (Exception e) {
								log.debug("pre-fail-state: " + result.getLateHold());
								log.error(e);
								r.fail(new RuntimeException("[wait::late] Timed out!"));
								return;
							}
							if (TIMING_LOG) {
								log.debug("getVideoPage::LATE_EXTRACTION_COMPLETE -> " + (System.currentTimeMillis() - t) + " ms.");
							}
						}
						final List<YouTubeVideo> videos = result.videos();
						if (videos == null) {
							log.error("Got a null YouTube video source list, panic.");
							r.complete(new Response(200, createStream(RELOAD_NOW_JSON)).setCustomMime(JSON_MIME));
							return;
						}
						log.debug("Got " + videos.size() + " video sources.");
						if (TIMING_LOG) {
							log.debug("getVideoPage::READ_VIDEOS -> " + (System.currentTimeMillis() - t) + " ms.");
						}
						telemetryData.waitFor(133, TimeUnit.MILLISECONDS);
						if (TIMING_LOG) {
							log.debug("getVideoPage::WAIT_TELEMETRY -> " + (System.currentTimeMillis() - t) + " ms.");
						}
						final boolean reduceLoad = vrp.reduceLoad;
						final boolean av1_only_lowRes = vrp.av1_only_lowRes;
						final variable<Boolean> has60FPS = new variable<Boolean>(false);
						final List<YouTubeVideo> audioStreams = FastLists.list();
						final List<YouTubeVideo> videoStreams = FastLists.list(18);
						final List<YouTubeVideo> combinedStreams = FastLists.list();
						final List<YouTubeVideo> specialStreams = FastLists.list();
						if (USE_PARALLEL_STREAM_GEN) {
							log.debug("sort::PARALLEL_STREAM");
							videos.parallelStream().forEach((video) -> {
								if (video == null) {
									log.warn("Got a null YouTubeVideo source. [e=" + video + "]");
								} else if (video.getFormatType() == FormatType.OTF_STREAM) {
									log.warn("Skipped processing an unsupported YouTubeVideo source. [e=" + video + "]");
								} else if (video.getFormatType() == FormatType.DASH_STREAM) {
									specialStreams.add(video);
								} else {
									final Format f = video.getFormat();
									if (f == null) {
										log.error("f was null! Skipping. [itag=" + video.getTag() + "]");
									} else if (reduceLoad && f.getHeight() > 1080) {
										return;
									} else {
										if (f.getFPS() == 60) {
											if (!has60FPS.value()) {
												has60FPS.set(true);
												log.debug("setHas60FPS to true.");
											}
										}
										if (!f.getAudioCodec().equals(AudioCodec.NONE) && f.getVideoCodec().equals(VideoCodec.NONE)) {
											if (vrp.openCodecsOnly && (!f.getAudioCodec().isOpen())) {
												return;
											} else {
												audioStreams.add(video);
											}
										} else if (f.getAudioCodec().equals(AudioCodec.NONE) && !f.getVideoCodec().equals(VideoCodec.NONE)) {
											if (f.isHDR() && vrp.enableHDR) {
												videoStreams.add(video);
											} else if (f.getVideoCodec().equals(VideoCodec.AV1) && !vrp.enableAV1) {
												return;
											} else if (ArrayUtils.contains(VideoCodec.FAMILY_VPX, f.getVideoCodec()) && !vrp.allowWebMs) {
												return;
											} else if (vrp.openCodecsOnly && !f.getVideoCodec().isOpenCodec()) {
												return;
											} else if (!f.isHDR()) {
												videoStreams.add(video);
											}
										} else if (!f.getAudioCodec().equals(AudioCodec.NONE) && !f.getVideoCodec().equals(VideoCodec.NONE)) {
											if (vrp.openCodecsOnly && !f.getVideoCodec().isOpenCodec()) {
												return;
											} else {
												combinedStreams.add(video);
											}
										} else {
											log.warn("Skipped video: " + video);
										}
									}
								}
							});
							log.debug("wait::PARALLEL_STREAM_PRE_SORT");
							BackportList.sort(audioStreams, AS_BITRATE_COMP);
							log.debug("wait::PARALLEL_STREAM_SORT[post-audio]");
							BackportList.sort(combinedStreams, CS_HEIGHT_COMP);
							log.debug("wait::PARALLEL_STREAM_SORT[post-comb]");
							MultiComparator.sort(videoStreams, VS_HEIGHT_COMP, VS_FPS_COMP);
							log.debug("wait::PARALLEL_STREAM_SORT[post-video]");
						} else {
							log.debug("sort::LEGACY");
							final Executable<Object> audioStream_gen = td.asyncSubmit(() -> {
								if (videos != null && !videos.isEmpty()) {
									for (int i = 0; i < videos.size(); i++) {
										final YouTubeVideo e = videos.get(i);
										if (e == null) {
											log.warn("Got a null YouTubeVideo source. [index=" + i + ",e=" + e + "]");
										} else {
											final Format f = e.getFormat();
											if (f == null) {
												log.error("f was null! Skipping. [itag=" + e.getTag() + "]");
											} else if (f.getAudioCodec() == null) {
												log.error("f.getAudioCodec was null! Skipping! [f=" + f + "]");
											} else if (!f.getAudioCodec().equals(AudioCodec.NONE) && f.getVideoCodec().equals(VideoCodec.NONE)) {
												if (vrp.openCodecsOnly && (!f.getAudioCodec().isOpen())) {
												} else {
													audioStreams.add(e);
												}
											}
										}
									}
								} else {
									log.error("videos == null/empty!");
								}
								BackportList.sort(audioStreams, new Comparator<YouTubeVideo>() {

									@Override
									public int compare(final YouTubeVideo o1, final YouTubeVideo o2) {
										if (o1 == null || o2 == null)
											return 0;
										return Integer.valueOf(o1.getFormat().getBitrate()).compareTo(o2.getFormat().getBitrate());
									}
								});
								return null;
							});
							log.debug("pre::asg_gen");
							final Executable<Object> videoStream_gen = td.asyncSubmit(() -> {
								for (int i = 0; i < videos.size(); i++) {
									final YouTubeVideo e = videos.get(i);
									if (e == null) {
										log.warn("Skipped processing a YouTubeVideo source. [i=" + i + ",e=" + e + "]");
									} else if (e.getFormatType() == FormatType.OTF_STREAM) {
										log.warn("Skipped processing an unsupported YouTubeVideo source. [i=" + i + ",e=" + e + "]");
									} else if (e.getFormatType() == FormatType.DASH_STREAM) {
										specialStreams.add(e);
									} else {
										final Format f = e.getFormat();
										if (f == null) {
											log.error("f was null! Skipping. [itag=" + e.getTag() + "]");
										} else if (f.getAudioCodec() == null) {
											log.error("f.getAudioCodec was null! Skipping! [f=" + f + "]");
										} else if (reduceLoad && f.getHeight() > 1080) {
											continue;
										} else if (f.getAudioCodec().equals(AudioCodec.NONE) && !f.getVideoCodec().equals(VideoCodec.NONE)) {
											if (f.isHDR() && vrp.enableHDR) {
												videoStreams.add(e);
											} else if (f.getVideoCodec().equals(VideoCodec.AV1) && !vrp.enableAV1) {
											} else if (ArrayUtils.contains(VideoCodec.FAMILY_VPX, f.getVideoCodec()) && !vrp.allowWebMs) {
											} else if (vrp.openCodecsOnly && !f.getVideoCodec().isOpenCodec()) {
											} else if (!f.isHDR()) {
												videoStreams.add(e);
											} else {
												log.warn("Unknown video: " + e);
											}
										}
									}
								}
								MultiComparator.sort(videoStreams, new Comparator<YouTubeVideo>() {

									@Override
									public int compare(final YouTubeVideo o1, final YouTubeVideo o2) {
										if (o1 == null || o2 == null)
											return 0;
										return Integer.valueOf(o1.getFormat().getHeight()).compareTo(o2.getFormat().getHeight());
									}
								}, new Comparator<YouTubeVideo>() {

									@Override
									public int compare(final YouTubeVideo o1, final YouTubeVideo o2) {
										if (o1 == null || o2 == null)
											return 0;
										return Integer.valueOf(o1.getFormat().getFPS()).compareTo(o2.getFormat().getFPS());
									}
								});
								return null;
							});
							log.debug("pre::vs_gen");
							final Executable<Object> combinedStream_gen = td.asyncSubmit(() -> {
								for (int i = 0; i < videos.size(); i++) {
									final YouTubeVideo e = videos.get(i);
									if (e == null) {
										log.warn("Skipped processing a YouTubeVideo source. [i=" + i + ",e=" + e + "]");
									} else {
										final Format f = e.getFormat();
										if (f == null) {
											log.error("f was null! Skipping. [itag=" + e.getTag() + "]");
										} else if (f.getAudioCodec() == null) {
											log.error("f.getAudioCodec was null! Skipping! [f=" + f + "]");
										} else {
											if (e.getFPS() == 60) {
												if (!has60FPS.value()) {
													has60FPS.set(true);
													log.debug("setHas60FPS to true.");
												}
											}
											if (!f.getAudioCodec().equals(AudioCodec.NONE) && !f.getVideoCodec().equals(VideoCodec.NONE)) {
												if (vrp.openCodecsOnly && !f.getVideoCodec().isOpenCodec()) {
												} else {
													combinedStreams.add(e);
												}
											}
										}
									}
								}
								BackportList.sort(combinedStreams, new Comparator<YouTubeVideo>() {

									@Override
									public int compare(final YouTubeVideo o1, final YouTubeVideo o2) {
										if (o1 == null || o2 == null)
											return 0;
										return Integer.valueOf(o1.getFormat().getHeight()).compareTo(o2.getFormat().getHeight());
									}
								});
								return null;
							});
							log.debug("pre::comb_gen");
							if (TIMING_LOG) {
								log.debug("getVideoPage::SUBMIT_VIDEO_SORTING -> " + (System.currentTimeMillis() - t) + " ms.");
							}
							log.debug("pre::await(ALL)");
							if (log.isTraceEnabled()) {
								log.trace("await[STREAMS]:");
								final Executable<?>[] awaits = new Executable<?>[]
									{ combinedStream_gen, audioStream_gen, videoStream_gen };
								for (final Executable<?> o : awaits)
									log.trace("\t" + o);
							}
							td.await(combinedStream_gen, audioStream_gen, videoStream_gen);
						}
						log.debug("output::object_video");
						if (TIMING_LOG) {
							log.debug("getVideoPage::WAIT_VIDEO_SORTING -> " + (System.currentTimeMillis() - t) + " ms.");
						}
						final UniJsonValue newVideo = UniJsonValue.object();
						final Executable<UniJsonValue> spStreamGen = td.asyncSubmit(() -> {
							final UniJsonValue arr = UniJsonValue.array();
							if (!specialStreams.isEmpty()) {
								for (final YouTubeVideo v : specialStreams) {
									if (v == null)
										continue;
									else {
										switch (v.getFormatType()) {
											case DASH_STREAM: {
												final UniJsonValue s = UniJsonValue.object();
												s.add("url", "/dash_translate" + urlizer(v.videoURL()));
												s.add("type", "STREAM_DASH");
												s.add("variant", "YOUTUBE_PROVIDED_STREAM");
												arr.add(s);
												break;
											}
											case STANDARD:
												break;
											default: {
												log.warn("[specialStreams] Unknown format type: " + v.getFormatType());
												break;
											}
										}
									}
								}
							}
							return arr;
						});
						if (TIMING_LOG) {
							log.debug("getVideoPage::SUBMIT_DASH_GEN -> " + (System.currentTimeMillis() - t) + " ms.");
						}
						final UniJsonValue pref = UniJsonValue.object();
						int opusStream = combinedStreams.size() - 1;
						{
							for (int i = audioStreams.size() - 1; i >= 0; i--) {
								final YouTubeVideo v = audioStreams.get(i);
								if (v == null)
									continue;
								else if (v.getFormat().getAudioCodec().equals(AudioCodec.OPUS) && v.getFormat().getVideoCodec().equals(VideoCodec.NONE)) {
									opusStream = i;
									break;
								}
							}
						}
						int aacStream = -1;
						{
							if (audioStreams.size() == 1) {
								final YouTubeVideo v = audioStreams.get(0);
								if (v.getFormat().getAudioCodec().equals(AudioCodec.AAC) && v.getFormat().getVideoCodec().equals(VideoCodec.NONE)) {
									aacStream = 0;
								}
							} else {
								for (int i = audioStreams.size() - 1; i >= 0; i--) {
									final YouTubeVideo v = audioStreams.get(i);
									if (v == null)
										continue;
									else if (v.getFormat().getAudioCodec().equals(AudioCodec.AAC) && v.getFormat().getVideoCodec().equals(VideoCodec.NONE)) {
										aacStream = i;
										break;
									}
								}
								if (aacStream == -1) {
									for (int i = 0; i < audioStreams.size(); i++) {
										final YouTubeVideo v = audioStreams.get(i);
										if (v == null)
											continue;
										else if (v.getFormat().getAudioCodec().equals(AudioCodec.AAC)
												&& v.getFormat().getVideoCodec().equals(VideoCodec.NONE)) {
											aacStream = i;
											break;
										}
									}
								}
								if (aacStream == -1) {
									warns.add(APIWarnings.get(7));
									log.dwarn("No AAC streams found.");
									aacStream = 0;
								}
							}
						}
						if (TIMING_LOG) {
							log.debug("getVideoPage::GENERATE_PREFERRED -> " + (System.currentTimeMillis() - t) + " ms.");
						}
						final Executable<UniJsonValue> asd_gen = td.asyncSubmit(() -> {
							final UniJsonValue audio = UniJsonValue.array(audioStreams.size());
							(vrp.allow_lazy_video_pref ? audioStreams.parallelStream() : audioStreams.stream()).forEach((s) -> {
								if (s == null) {
									log.warn("Expected a video object, got: " + s + " (asd_gen)");
								} else {
									final UniJsonValue stream = UniJsonValue.object(16);
									stream.add("itag", s.getTag());
									if (EXPOSE_IPs) {
										stream.add("url", s.videoURL());
									}
									if (NTUBE_ARCHIVER_MODE) {
										stream.add("proxied_url", s.videoURL());
									} else {
										stream.add("proxied_url", urlizer(s.videoURL(vrp.random_video_cdn)));
									}
									stream.add("format_bitrate", s.getFormat().getBitrate());
									stream.add("codec", VideoPageUtil.getAudioCodecType(s.getFormat().getAudioCodec()));
									stream.add("surroundAudio", s.getFormat().isHighAudioChannel());
									stream.add("init", s.getInitPayload());
									stream.add("index", s.getIndexPayload());
									stream.add("bitrate", s.getBitrate());
									stream.add("size", s.getFileSize());
									if (s.hasCodecMime()) {
										stream.add("mime", s.getCodecMime());
										stream.add("pmime", s.getPrettyMime());
									} else {
										stream.add("mime", "unknown");
										stream.add("pmime", "unknown");
									}
									if (isAPI17 && YouTubeExtractor.LIB_VERSION.newerThan("2.2")) {
										final UniJsonValue ecd = UniJsonValue.object(2);
										ecd.add("video", s.getEnhancedVideoCodec());
										ecd.add("audio", s.getEnhancedAudioCodec());
										stream.add("ecd", ecd);
									}
									if (s.getFormat().hasDRMFamilies()) {
										final UniJsonValue d = UniJsonValue.array();
										for (final DRMFamily f : s.getFormat().getDRMFamilies())
											d.add(f.name());
										stream.add("drmFamilies", d);
									}
									if (s.getFormat().hasTrackID())
										stream.add("trackID", s.getFormat().getTrackID());
									if (s.getFormat().hasTrackLanguage())
										stream.add("trackLang", s.getFormat().getTrackLanguage());
									stream.add("isDRC", s.getFormat().isDRCPresent());
									audio.add(stream);
								}
							});
							return audio;
						});
						final Executable<UniJsonValue> cmbs_gen = td.asyncSubmit(() -> {
							final UniJsonValue comb = UniJsonValue.array(combinedStreams.size());
							(vrp.allow_lazy_video_pref ? combinedStreams.parallelStream() : combinedStreams.stream()).forEach((s) -> {
								if (s == null) {
									log.warn("Expected a video object, got: " + s + " (cmbs_gen)");
								} else {
									final UniJsonValue stream = UniJsonValue.object(18);
									stream.add("itag", s.getTag());
									if (s.videoURL() == null) {
										log.warn("Video URL was null! [itag=" + s.getTag() + ",size=" + s.getSize() + "]");
										return;
									}
									if (s.isLiveStream()) {
										stream.add("url", urlizer(s.videoURL()) + "&ythls");
										if (EXPOSE_IPs) {
											stream.add("rawURL", s.videoURL());
										}
									} else {
										if (EXPOSE_IPs) {
											stream.add("url", s.videoURL());
										}
									}
									stream.add("quality", s.getFormat().getHeight());
									try {
										stream.add("width", Integer.parseInt(Splitters.splitString(s.getSize(), "x")[0]));
									} catch (Exception e) {
										log.error("Failed to add width!", e);
									}
									stream.add("fps", s.getFormat().getFPS());
									stream.add("codec", VideoPageUtil.getVideoCodecType(s.getFormat().getVideoCodec()));
									stream.add("liveStream", s.isLiveStream());
									stream.add("true_fps", s.getFPS());
									if (s.getFormat().hasDRMFamilies()) {
										final UniJsonValue d = UniJsonValue.array();
										for (final DRMFamily f : s.getFormat().getDRMFamilies())
											d.add(f.name());
										stream.add("drmFamilies", d);
									}
									if (s.getInitPayload() == null) {
										if (s.requiresSupplimentaryVideoAudio()) {
											log.warn(s.getTag() + ": initPayload is null! [got: " + s.getInitPayload() + "]");
										}
									} else {
										stream.add("init", s.getInitPayload());
									}
									if (s.getIndexPayload() == null) {
										if (s.requiresSupplimentaryVideoAudio()) {
											log.warn(s.getTag() + ": indexPayload is null! [got: " + s.getIndexPayload() + "]");
										}
									} else {
										stream.add("init", s.getIndexPayload());
									}
									if (isAPI17 && YouTubeExtractor.LIB_VERSION.newerThan("2.2")) {
										final UniJsonValue ecd = UniJsonValue.object(2);
										ecd.add("video", s.getEnhancedVideoCodec());
										ecd.add("audio", s.getEnhancedAudioCodec());
										stream.add("ecd", ecd);
									}
									stream.add("bitrate", s.getBitrate());
									stream.add("size", s.getFileSize());
									stream.add("isHDR", s.getFormat().isHDR());
									if (NTUBE_ARCHIVER_MODE) {
										stream.add("proxied_url", s.videoURL());
									} else if (s.isLiveStream()) {
										stream.add("proxied_url", urlizer(s.videoURL()) + "&ythls");
									} else {
										stream.add("proxied_url", urlizer(s.videoURL(vrp.random_video_cdn)));
									}
									if (s.hasCodecMime()) {
										stream.add("mime", s.getCodecMime());
										stream.add("pmime", s.getPrettyMime());
									}
									comb.add(stream);
								}
							});
							pref.add("comb", combinedStreams.size() - 1);
							return comb;
						});
						final int __opusStream = opusStream;
						final int __aacStream = aacStream;
						final Executable<UniJsonValue> vsd_gen = td.asyncSubmit(() -> {
							final UniJsonValue video = UniJsonValue.array(videoStreams.size());
							try {
								(vrp.allow_lazy_video_pref ? videoStreams.parallelStream() : videoStreams.stream()).forEach((s) -> {
									if (s == null) {
										log.warn("Expected a video object, got: " + s + " (vsd_gen)");
									} else {
										final UniJsonValue stream = UniJsonValue.object(18);
										stream.add("itag", s.getTag());
										if (EXPOSE_IPs) {
											stream.add("url", s.videoURL());
										}
										stream.add("quality", s.getFormat().getHeight());
										try {
											stream.add("width", Integer.parseInt(Splitters.splitString(s.getSize(), "x")[0]));
										} catch (Exception e) {
											log.error("Failed to add width!", e);
										}
										stream.add("fps", s.getFormat().getFPS());
										stream.add("codec", VideoPageUtil.getVideoCodecType(s.getFormat().getVideoCodec()));
										stream.add("init", s.getInitPayload());
										stream.add("index", s.getIndexPayload());
										stream.add("bitrate", s.getBitrate());
										stream.add("size", s.getFileSize());
										stream.add("true_fps", s.getFPS());
										if (!audioStreams.isEmpty()) {
											stream.add("audioPair", audioStreams.size() - 1);
										}
										if (s.hasCodecMime()) {
											stream.add("mime", s.getCodecMime());
											stream.add("pmime", s.getPrettyMime());
										}
										if (s.getFormat().hasDRMFamilies()) {
											final UniJsonValue d = UniJsonValue.array();
											for (final DRMFamily f : s.getFormat().getDRMFamilies())
												d.add(f.name());
											stream.add("drmFamilies", d);
										}
										if (isAPI17 && YouTubeExtractor.LIB_VERSION.newerThan("2.2")) {
											final UniJsonValue ecd = UniJsonValue.object(2);
											ecd.add("video", s.getEnhancedVideoCodec());
											ecd.add("audio", s.getEnhancedAudioCodec());
											stream.add("ecd", ecd);
										}
										if (isAPI17)
											stream.add("projectionType", s.getProjectionMode().getID());
										if (NTUBE_ARCHIVER_MODE) {
											stream.add("proxied_url", s.videoURL());
										} else {
											stream.add("proxied_url", urlizer(s.videoURL(vrp.random_video_cdn)));
										}
										if (vrp.force_aac_nplayer) {
											stream.add("mse_pair", __aacStream);
										} else if (ArrayUtils.contains(VideoCodec.FAMILY_VPX, s.getFormat().getVideoCodec())) {
											stream.add("mse_pair", __opusStream);
										} else if (s.getFormat().getVideoCodec().equals(VideoCodec.H264)) {
											stream.add("mse_pair", __aacStream);
										} else if (s.getFormat().getVideoCodec().equals(VideoCodec.AV1)) {
											if (s.getCodecMime().startsWith("video/mp4")) {
												stream.add("mse_pair", __aacStream);
											} else {
												stream.add("mse_pair", __opusStream);
											}
										}
										stream.add("isHDR", s.getFormat().isHDR());
										video.add(stream);
									}
								});
							} catch (Exception e) {
								log.error(e);
							}
							return video;
						});
						if (TIMING_LOG) {
							log.debug("getVideoPage::SUBMIT_VIDEO_DATA_GEN -> " + (System.currentTimeMillis() - t) + " ms.");
						}
						if (log.isTraceEnabled()) {
							log.trace("await[STREAM_GEN]:");
							final Executable<?>[] awaits = new Executable<?>[]
								{ asd_gen, cmbs_gen, vsd_gen };
							for (final Executable<?> o : awaits)
								log.trace("\t" + o);
						}
						td.awaitFor(10, TimeUnit.SECONDS, asd_gen, cmbs_gen, vsd_gen);
						if (TIMING_LOG) {
							log.debug("getVideoPage::AWAIT_VIDEO_DATA_GEN -> " + (System.currentTimeMillis() - t) + " ms.");
						}
						newVideo.add("audioStreams", asd_gen.waitTillCompletion());
						newVideo.add("combinedStreams", cmbs_gen.waitTillCompletion());
						newVideo.add("videoStreams", vsd_gen.waitTillCompletion());
						if (!audioStreams.isEmpty()) {
							final boolean onlySupportsH264 = telemetryData.result() != null && telemetryData.result().supportsH264
									&& !telemetryData.result().supportsAV1 && !telemetryData.result().supportsVP9;
							if (onlySupportsH264) {
								pref.add("audio", aacStream);
							} else {
								pref.add("audio", audioStreams.size() - 1);
							}
						}
						final int prefQ = vrp.dataSaving ? 360 : vrp.preferredQuality;
						if (!videoStreams.isEmpty() && ENABLE_NEW_SORTER) {
							final YouTubeVideo v = (YouTubeVideo) ArrayUtils.last(videoStreams.parallelStream()
									/* */
									.filter(vid -> vid != null && vid.getFormat() != null)
									/* */
									.filter(vid -> vid.getFormat().getHeight() <= prefQ && prefQ != -1)
									/* */
									.filter(vid -> {
										if (!vrp.enableAV1 && vid.getFormat().getVideoCodec().equals(VideoCodec.AV1))
											return false;
										if (!vrp.allowWebMs && (ArrayUtils.contains(VideoCodec.FAMILY_VPX, vid.getFormat().getVideoCodec())))
											return false;
										return true;
									}).sorted((current, next) -> {
										return Integer.valueOf(current.getFormat().getHeight()).compareTo(next.getFormat().getHeight());
									}).sorted((current, next) -> {
										return Integer.valueOf(current.getFormat().getFPS()).compareTo(next.getFormat().getFPS());
									}).toArray());
							int prefVideoNew = -1;
							if (v == null) {
								log.warn("Failed to sort using parallel sorting, falling back to legacy!");
								prefVideoNew = GetVideoAsyncPage.this.sort_legacy(videoStreams, prefQ, vrp);
							} else {
								for (int i = videoStreams.size() - 1; i >= 0; i--) {
									final YouTubeVideo vse = videoStreams.get(i);
									if (vse == null)
										continue;
									else if (vse.getTag() == v.getTag()) {
										prefVideoNew = i;
										break;
									}
								}
							}
							if (prefVideoNew == -1)
								throw new RuntimeException("new_sorter: Failed to get new video pref!");
							pref.add("video", prefVideoNew);
						} else if (!videoStreams.isEmpty()) {
							final int prefVideo = GetVideoAsyncPage.this.sort_legacy(videoStreams, prefQ, vrp);
							final YouTubeVideo result1 = videoStreams.get(prefVideo);
							log.trace("nAPI Result: " + result1);
							int prefVideoNew = prefVideo;
							final UniJsonValue video = vsd_gen.waitTillCompletion();
							for (int i = 0; i < video.size(); i++) {
								final UniJsonValue vid = video.get(i);
								if (vid.getInt("itag", -1) == result1.getTag()) {
									prefVideoNew = i;
									break;
								}
							}
							pref.add("video", prefVideoNew);
						}
						if (TIMING_LOG) {
							log.debug("getVideoPage::GENERATE_PREF_VIDEO -> " + (System.currentTimeMillis() - t) + " ms.");
						}
						log.debug("wait::vid_gen");
						td.awaitFor(1, TimeUnit.SECONDS, vsd_gen, asd_gen, cmbs_gen);
						if (TIMING_LOG) {
							log.debug("getVideoPage::AWAIT_METADATA_GENERATION -> " + (System.currentTimeMillis() - t) + " ms.");
						}
						newVideo.add("preferred", pref);
						if (log.isTraceEnabled()) {
							log.trace("await[PRIMARY]:");
							final Executable<?>[] awaits = new Executable<?>[]
								{ spStreamGen, metadata_gen, related_gen, captions_gen, env_gen, drm_gen };
							for (final Executable<?> o : awaits)
								log.trace("\t" + o);
						}
						log.debug("wait::main_gen");
						td.awaitFor(10, TimeUnit.SECONDS, spStreamGen, metadata_gen, related_gen, captions_gen, env_gen, drm_gen);
						log.debug("wait::metadata");
						/*
						 * If awaitFor didn't work, too bad! Explosions will occur below (as they
						 * should)
						 */
						newVideo.add("specialStreams", spStreamGen.result());
						out.add("metadata", metadata_gen.result());
						out.add("environment", env_gen.result());
						if (TIMING_LOG) {
							log.debug("getVideoPage::AWAIT_METADATA -> " + (System.currentTimeMillis() - t) + " ms.");
						}
						log.debug("wait::related");
						if (related_gen.hasFailed()) {
							log.warn("related_gen has failed, printing error.");
							log.error(related_gen.getException());
						} else if (related_gen.result() == null) {
							out.add("related", UniJsonValue.array());
						} else {
							out.add("related", related_gen.result());
							log.debug("result from wait: " + related_gen.result().length());
						}
						if (TIMING_LOG) {
							log.debug("getVideoPage::AWAIT_RELATED -> " + (System.currentTimeMillis() - t) + " ms.");
						}
						log.debug("wait::drm[" + drm_gen + "]");
						if (drm_gen.result() != null) {
							out.add("drm", drm_gen.result());
						}
						if (TIMING_LOG) {
							log.debug("getVideoPage::AWAIT_DRM -> " + (System.currentTimeMillis() - t) + " ms.");
						}
						log.debug("wait::chapters");
						if (chapters_gen.result() != null) {
							out.add("chapters", chapters_gen.result());
						}
						if (TIMING_LOG) {
							log.debug("getVideoPage::AWAIT_CHAPTERS -> " + (System.currentTimeMillis() - t) + " ms.");
						}
						log.debug("wait::captions");
						if (captions_gen.result() != null) {
							out.add("captions", captions_gen.result());
						}
						if (TIMING_LOG) {
							log.debug("getVideoPage::AWAIT_CAPTIONS -> " + (System.currentTimeMillis() - t) + " ms.");
						}
						log.debug("wait::playlist_gen[" + playlist_gen + "]");
						try {
							if (playlist_gen.result() != null) {
								if (playlist_gen.result() != null) {
									out.add("playlist", playlist_gen.result());
								} else {
									log.warn("playlist == null!");
								}
							}
						} catch (Exception e) {
							log.debug("pre-fail-state: " + playlist_gen);
							r.fail(new NullPointerException("[wait::playlist_gen] Timed out!"));
							return;
						}
						log.debug("wait::dash");
						final String f;

						if (vrp.enableDASH) {
							log.debug("[DJS] GetVideoAsyncPage->DashMPDGenerator2::make");
							f = DashMPDGenerator2.make(videos, rq, metadata, !vrp.enableAV1, vrp.dashRemove30FPS, vrp.dashRemove60FPS, has60FPS, vrp.allowWebMs,
									reduceLoad, av1_only_lowRes, vrp.audioOnly, vrp.random_video_cdn, vrp.dataSaving);
						} else {
							f = null;
						}

						if (f == null && vrp.enableDASH) {
							log.warn("DASH-MPD generation failed!");
						} else if (f == null && !vrp.enableDASH) {
						} else if (vrp.dash_use_server_caching) {
							newVideo.add("muxedPayload", DJSCache.allocateCache(f) + "&cached");
						} else if (vrp.dash_use_compression) {
							newVideo.add("muxedPayload", urlizer(f));
						} else {
							newVideo.add("muxedPayload", UniBase64.encodeFromString(f));
						}
						if (TIMING_LOG) {
							log.debug("getVideoPage::AWAIT_PLAYLISTS -> " + (System.currentTimeMillis() - t) + " ms.");
						}
						out.add("videos", newVideo);
						if (TIMING_LOG) {
							log.debug("getVideoPage::FINISH -> " + (System.currentTimeMillis() - t) + " ms.");
						}
						log.debug("Finished DJS_MUXER_GEN.");
						if (Arguments.notAnArgument(nTubeArguments._disableGC)) {
							td.asyncSubmit(() -> {
								result.clear();
								audioStreams.clear();
								videoStreams.clear();
								combinedStreams.clear();
								specialStreams.clear();
								videos.clear();
								log.trace("Ran GC. [GVP::threaded]");
								return null;
							});
						}
						if (!r.complete(new Response(200, out, out).setCustomMime(JSON_MIME))) {
							log.error("complete(Response) failed! [status: " + r.status() + "]");
							r.kill();
						} else
							log.debug("Finished request.");
						return;
					}
				}
			});
			if (!task_accepted)
				log.warn("[GetVideoPage-async] Callback was refused!");
		}
		return;
	}

	private int sort_legacy(final List<YouTubeVideo> videoStreams, final int prefQ, final VideoRequestProfile vrp) {
		boolean prevIsAV1 = false;
		int prefVideo = -1;
		int prevVQ = 0;
		int prevFPS = 30;
		int index = 0;
		for (final YouTubeVideo e : Variables.requireNonNullObject(videoStreams, "videoStreams")) {
			if (e == null || e.getFormat() == null)
				continue;
			if (prevVQ == 0 && index == 0) {
				prevVQ = e.getFormat().getHeight();
				prevFPS = e.getFormat().getFPS();
				prefVideo = index;
				prevIsAV1 = e.getFormat().getVideoCodec().equals(VideoCodec.AV1);
			}
			if (e.getFormat().getHeight() > prefQ && prefQ != -1) {
				if (log.isTraceEnabled()) {
					log.trace("skip (>): " + e);
				}
				index += 1;
				continue;
			}
			if (!vrp.allowWebMs && (ArrayUtils.contains(VideoCodec.FAMILY_VPX, e.getFormat().getVideoCodec()))) {
				if (log.isTraceEnabled()) {
					log.trace("skip (no-webm): " + e);
				}
				index += 1;
				continue;
			}
			if ((vrp.enableAV1 && prevIsAV1 && e.getFormat().getHeight() == prevVQ && e.getFormat().getFPS() > prevFPS)
					|| (e.getFormat().getHeight() > prevVQ || (e.getFormat().getHeight() == prevVQ && e.getFormat().getFPS() > 30))
					|| (vrp.enableAV1 && e.getFormat().getHeight() == prevVQ && e.getFormat().getVideoCodec().equals(VideoCodec.AV1))) {
				prevVQ = e.getFormat().getHeight();
				prevFPS = e.getFormat().getFPS();
				prefVideo = index;
				prevIsAV1 = e.getFormat().getVideoCodec().equals(VideoCodec.AV1);
				if (log.isTraceEnabled()) {
					log.trace("upgraded: " + e);
				}
				index += 1;
				continue;
			} else {
				if (log.isTraceEnabled()) {
					log.trace("skip (DN): " + e);
					log.trace("prevFPS: " + prevFPS);
				}
				index += 1;
			}
		}
		return prefVideo;
	}

	/**
	 * Deprecate and remove BitChute support, we might support it in the future once
	 * the ExtendedExecutor API is published, until then we'll drop it as it is no
	 * longer worthy of being provided by nTube and there are other free
	 * alternatives that provide the right to express opinion and views.
	 * 
	 * > Removed in 1.5.4.
	 */
	@SuppressWarnings("resource")
	private void __bitchute(final VideoRequestProfile vrp, final ControllableFutureTask<Response> r) {
		final UniJsonValue output = UniJsonValue.object();
		output.add("error_id", "BITCHUTE_NO_LONGER_AVAILABLE");
		output.add("error_message", "BitChute is no longer supported as of 1.5.4, sorry about that.");
		r.complete(new Response(200, output, output).setCustomMime(JSON_MIME));
		return;
	}

}
